#include "TerrainManager.h"
#include "QuadTree.h"


TerrainManager::TerrainManager(Ogre::SceneManager *iSceneMgr, QuadTree<QTreeNodeData>* iQuadtree) :
	mQuadtree(iQuadtree),
	mSceneManager(iSceneMgr),
	mInstances(),
	mInstanceCursor(0),
	mAddNodeToRenderFunctor(AddNodeToRender<QTreeNodeData>(
		static_cast<size_t>(iQuadtree->GetWorldResolution()),
		static_cast<size_t>(iQuadtree->GetMinCellResolution()),
		iQuadtree->GetMaxNumLevels(),
		iSceneMgr,
		1000.0f,
		20.0f))
{
	mAddNodeToRenderFunctor.SetTerrainManager(this);

	mMaterialName = Ogre::String("TerrainMaterial");

	BuildPatchMesh();
	// Creates a dummy instance to prevent Ogre destroying instance-property
	// vertex declaration when the first batch becomes empty :(
	mInstances.push_back(mInstanceManager->createInstancedEntity(mMaterialName));
	mInstances.front()->setPosition(Ogre::Vector3(-1000000.0f, -1000000.0f, 0.0f));
	++mInstanceCursor;
}



TerrainManager::~TerrainManager(void)
{
	(*mInstanceManager->getInstanceBatchIterator(mMaterialName).begin())->removeInstancedEntity(mInstances.front());
	mInstanceManager->cleanupEmptyBatches();
}



void TerrainManager::Update(const Ogre::FrameEvent& iEvent)
{
	mQuadtree->PreOrderTraversal(mAddNodeToRenderFunctor);

	ClearUnusedPatches();
}



void TerrainManager::BuildPatchGeometry(size_t iNumVerticesPerSide, std::vector<float>& oVertexData,
	std::vector<Ogre::int16>& oIndexData, size_t& oVerticesCount, size_t& oIndicesCount) const
{
	size_t numQuadsPerSide = iNumVerticesPerSide - 1;
	float verticesUVStep = 1.0f / numQuadsPerSide;
	numQuadsPerSide += 2; // +2 for skirts purpose...
	size_t numVerticesPerSideWithSkirts = iNumVerticesPerSide + 2; // +2 for skirts purpose...
	size_t triaglesCount = numQuadsPerSide * numQuadsPerSide * 2;

	oIndicesCount = triaglesCount * 3;
	oVerticesCount = numVerticesPerSideWithSkirts * numVerticesPerSideWithSkirts; 

	// allocate a temporary buffer to store the patch geometry vertices
	std::vector<float> outVertexData(oVerticesCount * 2);

	size_t currentIndex = 0;
	for(int i = -1; i <= static_cast<int>(iNumVerticesPerSide); ++i)
	{
		for(int j = -1; j <= static_cast<int>(iNumVerticesPerSide); ++j)
		{
			outVertexData[currentIndex++] = static_cast<float>(j) * verticesUVStep;
			outVertexData[currentIndex++] = static_cast<float>(i) * verticesUVStep;
		}
	}

	// allocate a temporary buffer to store the patch geometry indexes
	std::vector<Ogre::int16> outIndeces(oIndicesCount);

	currentIndex = 0;
	for(size_t i = 0; i < numQuadsPerSide; ++i)
	{
		for(size_t j = 0; j < numQuadsPerSide; ++j)
		{
			if((i + j) % 2 == 0)
			{
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j;
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j + 1;
				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j;

				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j;
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j + 1;
				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j + 1;
			}
			else
			{
				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j;
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j;
				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j + 1;

				outIndeces[currentIndex++] = i * numVerticesPerSideWithSkirts + j + 1;
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j;
				outIndeces[currentIndex++] = (i + 1) * numVerticesPerSideWithSkirts + j + 1;
			}
		}
	}

	// swap the vectors data to avoid useless allocations
	oVertexData.swap(outVertexData);
	oIndexData.swap(outIndeces);
}



void TerrainManager::BuildPatchMesh( void )
{
	Ogre::ResourcePtr terrainMat = Ogre::MaterialManager::getSingleton().getByName(mMaterialName);

	// load the material with the bound shader
	terrainMat->load();

	auto single = ::Ogre::MeshManager::getSingletonPtr();
	mTerrainPatchMesh = ::Ogre::MeshManager::getSingletonPtr()->createManual("TerrainPatchMesh", "General");

	Ogre::SubMesh* terrainPatchSubMesh = mTerrainPatchMesh->createSubMesh();

	size_t verticesCount = 0;
	size_t indicesCount = 0;
	std::vector<float> vertexData;
	std::vector<Ogre::int16> indexData;

	// get geometry data to build vertex and index buffers
	BuildPatchGeometry(PATCH_NUM_VERTICES_PER_SIDE, vertexData, indexData, verticesCount, indicesCount);

	// we first create a VertexData
	Ogre::VertexData* data = OGRE_NEW Ogre::VertexData();

	// then, we link it to our SubMesh :
	terrainPatchSubMesh->useSharedVertices = false;
	terrainPatchSubMesh->vertexData = data;
	// we have to provide the number of vertices we'll put into this SubMesh
	data->vertexCount = verticesCount;
	data->vertexStart = 0;

	// then we can create our VertexDeclaration
	Ogre::VertexDeclaration* vertexDeclPos = data->vertexDeclaration;
	vertexDeclPos->addElement(0, 0, Ogre::VET_FLOAT2, Ogre::VES_POSITION);

	// build the vertex buffer
	Ogre::HardwareVertexBufferSharedPtr vertsBuff = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
		vertexDeclPos->getVertexSize(0), indicesCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

	vertsBuff->writeData(0, verticesCount * sizeof(float) * 2, vertexData.data());

	Ogre::VertexBufferBinding* bind = data->vertexBufferBinding;
	bind->setBinding(0, vertsBuff);

	// build the index buffer
	Ogre::HardwareIndexBufferSharedPtr indexBuff = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
		Ogre::HardwareIndexBuffer::IT_16BIT, indicesCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

	terrainPatchSubMesh->indexData->indexBuffer = indexBuff;		// The pointer to the index buffer
	terrainPatchSubMesh->indexData->indexCount = indicesCount;		// The number of indices we'll use
	terrainPatchSubMesh->indexData->indexStart = 0;					// The offset from the beginning

	indexBuff->writeData(0, indicesCount * sizeof(Ogre::int16), indexData.data());

	mTerrainPatchMesh->_setBounds(Ogre::AxisAlignedBox(-12800.0f , -12800.0f, -12800.0f, 12800.0f, 12800.0f, 12800.0f));
	mTerrainPatchMesh->_setBoundingSphereRadius(12800.0f);

	mInstanceManager = mSceneManager->createInstanceManager("TerrainManager", "TerrainPatchMesh", 
		"General", Ogre::InstanceManager::HWInstancingBasic,
		PATCH_INSTANCES_DEFAULT_NUM, 0);

	mInstanceManager->setBatchesAsStaticAndUpdate(false);
}



void TerrainManager::AddPatch(const float iPosX, const float iPosY, const size_t iLOD)
{
	Ogre::InstancedEntity *ent;

	if(mInstanceCursor < mInstances.size())
	{
		// we have an available instance, so we can avoid creating a new one
		ent = mInstances[mInstanceCursor];
	}
	else 
	{
		// create a new patch instance
		ent = mInstanceManager->createInstancedEntity(mMaterialName);
		mInstances.push_back(ent);
	}

	++mInstanceCursor;

	// set the instance parameters (we use position because Ogre doesn't allow custom parameters)
	ent->setPosition(Ogre::Vector3(iPosX, static_cast<float>(iLOD), iPosY));
}


// remove the unused instances from the used batches
void TerrainManager::ClearUnusedPatches(void)
{
	size_t numInstances = mInstances.size();
	size_t unusedInstances = numInstances - mInstanceCursor;

	if(unusedInstances > 0)
	{
		size_t batchOffset = mInstanceCursor / PATCH_INSTANCES_DEFAULT_NUM;
		size_t instanceOffset = mInstanceCursor % PATCH_INSTANCES_DEFAULT_NUM;
		
		size_t firstUnusedBatchUnusedInstances = PATCH_INSTANCES_DEFAULT_NUM - instanceOffset;
		if(firstUnusedBatchUnusedInstances > unusedInstances)
			firstUnusedBatchUnusedInstances = unusedInstances;

		size_t middleUnusedBatches = (unusedInstances - firstUnusedBatchUnusedInstances) / PATCH_INSTANCES_DEFAULT_NUM;
		size_t lastUnusedBatchUnusedInstances = (unusedInstances - firstUnusedBatchUnusedInstances) % PATCH_INSTANCES_DEFAULT_NUM;

		auto batchIt = mInstanceManager->getInstanceBatchIterator(mMaterialName).begin() + batchOffset;
		auto instanceIt = mInstances.begin() + mInstanceCursor;

		for(size_t i = 0; i < firstUnusedBatchUnusedInstances; ++i)
		{
			(*batchIt)->removeInstancedEntity(*instanceIt++);
		}

		++batchIt;

		for(size_t i = 0; i < middleUnusedBatches; ++i)
		{
			for(int j = 0; j < PATCH_INSTANCES_DEFAULT_NUM; ++j)
			{
				(*batchIt)->removeInstancedEntity(*instanceIt++);
			}
			++batchIt;
		}

		for(size_t i = 0; i < lastUnusedBatchUnusedInstances; ++i)
		{
			(*batchIt)->removeInstancedEntity(*instanceIt++);
		}

		mInstances.resize(mInstanceCursor);

		mInstanceManager->cleanupEmptyBatches();
	}

	mInstanceCursor = 1;
}